package com.lry.rpc.starter.util;

import com.lry.rpc.starter.annotation.RpcService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;


@Slf4j
public class ScannerUtil {


    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private String resourcePattern = DEFAULT_RESOURCE_PATTERN;

    private MetadataReaderFactory metadataReaderFactory;

    private ResourcePatternResolver resourcePatternResolver;

    private Environment environment;

    private final List<TypeFilter> includeFilters = new LinkedList<>();

    private final List<TypeFilter> excludeFilters = new LinkedList<>();


    private final MetadataReaderFactory getMetadataReaderFactory() {
        if (this.metadataReaderFactory == null) {
            this.metadataReaderFactory = new CachingMetadataReaderFactory();
        }
        return this.metadataReaderFactory;
    }

    private ResourcePatternResolver getResourcePatternResolver() {
        if (this.resourcePatternResolver == null) {
            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        return this.resourcePatternResolver;
    }

    private final Environment getEnvironment() {
        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
        return this.environment;
    }

    private String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(getEnvironment().resolveRequiredPlaceholders(basePackage));
    }

    private Set<Class<?>> doScan(String basePackage) {
        Set<Class<?>> classes = new HashSet();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            for (Resource resource : resources) {

                if (!resource.isReadable()) {
                    continue;
                }
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

                    if ((includeFilters.size() == 0 && excludeFilters.size() == 0) || isCandidateComponent(metadataReader)) {

                        classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
                    }
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to read candidate component class: " + resource, ex);
                }
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return classes;
    }

    private boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return true;
            }
        }
        return false;
    }


    /**
     * @Deprecated 扫描包路径下注解类
     * @Author ChenBo 2022/1/13 18:36
     */
    public static Set<Class<?>> scanAnnotation (String  basePackage, Class<? extends Annotation>... annotations) {
        ScannerUtil scanUtil = new ScannerUtil();
        for (Class anno : annotations) {
            scanUtil.includeFilters.add(new AnnotationTypeFilter(anno));
        }
        return scanUtil.doScan(basePackage);
    }


    /**
     * @Deprecated 扫描包路径下的类
     * @Author ChenBo 2022/1/13 18:36
     */
    public static Set<Class<?>> scan (String basePackage) {
        ScannerUtil scanUtil = new ScannerUtil();
        return scanUtil.doScan(basePackage);
    }


    public static void main(String[] args) {

        Set<Class<?>> set = ScannerUtil.scanAnnotation("", RpcService.class);

        System.out.println(set.size());

    }

}
